package com.blog.aspect;

import com.blog.annotation.RequiredLog;
import com.blog.pojo.Log;
import com.blog.service.LogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 日志切面定义，切面中会包含两大部分:
 * 1.切入点(要织入扩展业务的目标方法集合)：一个切面中可以有多个。
 * 2.通知方法(要在切入点位置执行拓展业务的方法)：一个切面中可以有多个。
 */
@Order(-1)
@Component
@Aspect
public class LogAspect {

    /**基于注解方式定义切入点
     * 1. 通过Pointcut注解定义切入点
     * 2. @annotation注解内部是一个注解(这个注解描述的方法是切入点方法)
     */
    @Pointcut("@annotation(com.blog.annotation.RequiredLog)")
    public void doLog(){}

    /**
     * 业务:记录谁在什么时间、执行了什么操作、访问了什么方法、传递了什么参数、耗时是多少，操作状态如何？
     * @Around注解描述的方法为环绕通知方法，这个方法内部可以手动调用目标执行链。
     * @param jp 连接点对象，封装了要执行的目标方法信息,在环绕通知中，连接点类型
     *           只能是ProceedingJoinPoint。
     * @return 目标业务方法的执行结果
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp)throws Throwable{
        long start=System.currentTimeMillis();
        long end=0;
        long time=0;//耗时
        int status=1;//1 操作状态，表示ok，0表示error
        String errorMsg=null;//错误信息
        try {
            //执行目标业务链
            Object proceed = jp.proceed();
            end=System.currentTimeMillis();
            return proceed;
        }catch (Throwable e){
            end=System.currentTimeMillis();
            status=0;
            errorMsg=e.getMessage();
            throw e;
        }finally {
            time=end-start;
            saveUserLog(jp,time,status,errorMsg);
        }
    }

    void saveUserLog(ProceedingJoinPoint jp,Long time,int status,String errorMsg) throws NoSuchMethodException, JsonProcessingException {
       //1.获取用户行为日志
        //1.1获得用户ip
        ServletRequestAttributes requestAttributes=
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String ip=request.getRemoteAddr();
        //1.2获取登陆用户
        String username=(String)request.getSession().getAttribute("loginUser");;//假数据
        //1.3获取用户操作
        MethodSignature ms= (MethodSignature) jp.getSignature();
        Class<?> targetCls=jp.getTarget().getClass();
        Method targetMethod=targetCls.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
        RequiredLog annotation = targetMethod.getAnnotation(RequiredLog.class);
        String operation=annotation.operation();
        //1.4获取用户访问的方法
        String method=targetCls.getName()+"."+targetMethod.getName();
        //1.5获取方法执行时传入的参数
        Object[] params=jp.getArgs();
        //2.封装日志信息
       Log log=new Log();
       log.setIp(ip);
       log.setUsername(username);
       log.setCreatedTime(new Date());
       log.setOperation(operation);
       log.setMethod(method);
       log.setParams(new ObjectMapper().writeValueAsString(params));
       log.setStatus(status);
       log.setError(errorMsg);
       log.setTime(time);
      //3.将用户日志写入到数据库
       //System.out.println(log);
/*     new Thread(()->{//创建线程是耗时操作，每个线程也要默认占用1M的内存。
            logService.insert(log);
       }).start();*/
        logService.insert(log);
    }
    @Autowired
    private LogService logService;
}
